﻿namespace SpikingAnalyticsLib.ResponseFingerPrint

open System.IO

//=================================================================================================

/// A grid or table view of multi-column data
// Particularly useful for allowing side-by-side comparisons of multiple tests
// where each test produces a coluumn of data
// e.g. the neural response to different patterns over multiple frames
type GridView private () =

    // Retrieve a summary (percent passed) from the score data
    // Report on percentage correct for a single test
    static let GetScoreSummary (scoreData:_ list) =
        let correctScores =
            scoreData
            |> Seq.filter (fun (correct, _, _) -> correct)
            |> Seq.length
        sprintf "%d%%"  (correctScores * 100 / scoreData.Length)

    // Get a string representation of each score (with possible mark up for scores that don't pass)
    // Given a list of scores, a pass/fail flag, and the number of coincident spikes, extract the result line
    static let GetScoreStrings scoreData markupNonPassed showCoincidentSpikes =
        scoreData
        |> Seq.map (fun (passed, score, coincidentSpikes) ->
                match showCoincidentSpikes, markupNonPassed, passed with
                | true, true, false -> sprintf "%d\t%f*" coincidentSpikes score
                | true, false, _ -> sprintf "%d\t%f" coincidentSpikes score
                | false, true, false -> sprintf "%f*" score
                | _, _, _ -> sprintf "%f" score
            )

    // Write to the console
    static let GenerateConsoleScoreReport header scoreSummary scoreStrings (selectedPatternBySecond:Map<int, _>) =
        printfn "%s" header
        scoreStrings
        |> Seq.iteri (fun index scoreString ->
                let presentedPattern = selectedPatternBySecond.[index]
                printfn "%d\t%s\t\t%s" index presentedPattern scoreString
            )
        printfn "%s" scoreSummary

    // Optionally write to a file
    static let GenerateFileScoreReport header scoreSummary scoreStrings (selectedPatternBySecond:Map<int, _>) (path:string) =
        let scoreStrings =
            scoreStrings
            |> Seq.mapi (fun index scoreString ->
                    let presentedPattern = selectedPatternBySecond.[index]
                    sprintf "%d\t%s\t%s" index presentedPattern scoreString
                )
            |> Seq.toList
        File.WriteAllLines(path, scoreStrings)

    // Each column of scores is displayed either by itself or paired with the corresponding column of windowSpike counts
    // If alwaysShowCoincidentWindowSpikes is set to true, all score columns are paired with their windowSpike counts
    // Otherwise, only the first column is paired
    // (in this case the first column is assumed to be a test pattern and other columns are e.g. the null column)
    static let ShowGrid scoreData numFrames selectedPatternBySecond alwaysShowCoincidentWindowSpikes (outputPath:string option) =

        // produce the 'percent correct' summaries for each column of the test
        let scoreSummary =
            scoreData
            |> Seq.map (fun testScoreData ->
                    GetScoreSummary testScoreData
                )
            |> String.concat " "

        // produce one concatenated line of results for each frame
        let resultLines =

            // recreate the grid as just strings
            let resultArray =
                scoreData
                |> Seq.mapi (fun index testScoreData ->
                        // show the coincident window spikes for the first column only
                        let showCoincidentWindowSpikes =
                            if alwaysShowCoincidentWindowSpikes then
                                true
                            else
                                (index = 0)
                        GetScoreStrings testScoreData false showCoincidentWindowSpikes
                        |> Seq.toArray
                    )
                |> Seq.toArray

            // concatenate results across each row (i.e. across tests)
            Seq.init numFrames (fun frameIndex ->
                    // for each test, retrieve the result for the given frame index
                    resultArray
                    |> Array.map (fun testResults -> testResults.[frameIndex])
                    |> String.concat "\t"
                )

        // report the results
        let header = "FrameIndex\tPresentedPattern\tCoincidentWindowSpikes\tNormalisedValues..."

        // create console report
        GenerateConsoleScoreReport header scoreSummary resultLines selectedPatternBySecond

        // create file report
        if outputPath.IsSome then
            GenerateFileScoreReport header scoreSummary resultLines selectedPatternBySecond outputPath.Value

    /// Display the grid (and optionally save to a file)
    static member Show(scoreData, numFrames, selectedPatternBySecond, alwaysShowCoincidentWindowSpikes, outputPath) =
        ShowGrid scoreData numFrames selectedPatternBySecond alwaysShowCoincidentWindowSpikes outputPath

    /// Write a score report to the console
    static member ViewScoreReport(scoreData, header, markupNonPassed, showCoincidentSpikes, selectedPatternBySecond:Map<int, _>) =
        let scoreSummary = GetScoreSummary scoreData
        let scoreStrings = GetScoreStrings scoreData markupNonPassed showCoincidentSpikes
        GenerateConsoleScoreReport header scoreSummary scoreStrings (selectedPatternBySecond:Map<int, _>)

    /// Save a score report to the specified file
    static member SaveScoreReport(scoreData, header, markupNonPassed, showCoincidentSpikes, selectedPatternBySecond:Map<int, _>, outputPath:string) =
        let scoreSummary = GetScoreSummary scoreData
        let scoreStrings = GetScoreStrings scoreData markupNonPassed showCoincidentSpikes
        GenerateFileScoreReport header scoreSummary scoreStrings selectedPatternBySecond outputPath

//=================================================================================================
